//------------------------------------------------------------------------------ 
//      Copyright (c) 2004-2009, Be Nanashi-San 
// 
//      Permission is hereby granted, free of charge, to any person obtaining a 
//      copy of this software and associated documentation files (the "Software"), 
//      to deal in the Software without restriction, including without limitation 
//      the rights to use, copy, modify, merge, publish, distribute, sublicense, 
//      and/or sell copies of the Software, and to permit persons to whom the 
//      Software is furnished to do so, subject to the following conditions: 
// 
//      The above copyright notice and this permission notice shall be included in 
//      all copies or substantial portions of the Software. 
// 
//      THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
//      IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
//      FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
//      AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
//      LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
//      FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
//      DEALINGS IN THE SOFTWARE. 
// 
//      File Name:   BBSHandler.cpp
//      Author:      Be Nanashi-San 
//      Description: BBS Board list display class. 
//------------------------------------------------------------------------------
#include "BoardListView.h"
#include "BoardOutLineListView.h"
#include <Directory.h>
#include <Entry.h>
#include <File.h>
#include <kernel/fs_attr.h>
#include <DataIO.h>
#include <Window.h>
#include <UTF8.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "BoardItem.h"
#include "BBSHandler.h"
#include "NetUtil.h"
#include "Util.h"
#include "2chPositive.h"
#include "setting.h"
#include "Stack.h"

#define		kBBS_ARCHIVE		"BBS_archive"
static const unsigned int kNAMEBUFSIZ = 256;

////////////////////////////////////////////////////////////////////////////////
// 
//
//
////////////////////////////////////////////////////////////////////////////////
BBSHandler::BBSHandler(const char *BBSname)
:fBBSname(NULL),
fSuperItem(NULL)
//fPrefView(NULL)
{
	fSuperItem = NULL;
	SetName(BBSname);
}
////////////////////////////////////////////////////////////////////////////////
// 
//
//
////////////////////////////////////////////////////////////////////////////////
BBSHandler::~BBSHandler()
{
	if(fBuf.Seek(0, SEEK_END)){
		BDirectory dir;
		findcachedir(&dir, kBBS_DIR_NAME);
		BFile savefile(&dir, this->GetName(), B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE);
		savefile.Write(fBuf.Buffer(), fBuf.BufferLength());
	}
}

////////////////////////////////////////////////////////////////////////////////
// 
//
//
////////////////////////////////////////////////////////////////////////////////
const char *
BBSHandler::GetName() const
{
	return fBBSname.String();
}
////////////////////////////////////////////////////////////////////////////////
// 
//
//
////////////////////////////////////////////////////////////////////////////////
void
BBSHandler::SetName(const char *name)
{
	fBBSname = name;
}
////////////////////////////////////////////////////////////////////////////////
// 
//
//
////////////////////////////////////////////////////////////////////////////////
status_t
BBSHandler::LoadFromInet()
{
	return BoardRequested(fBuf);
}
////////////////////////////////////////////////////////////////////////////////
// 
//
//
////////////////////////////////////////////////////////////////////////////////
status_t
BBSHandler::LoadFromLocal()
{
	return this->defaultLocalReader();
}
////////////////////////////////////////////////////////////////////////////////
// 
//
//
////////////////////////////////////////////////////////////////////////////////
void
BBSHandler::DrawBoardList(BoardListView *owner)
{
	DrawBoardListReuested(owner, fBuf);
}
////////////////////////////////////////////////////////////////////////////////
// 
//
//
////////////////////////////////////////////////////////////////////////////////
status_t
BBSHandler::defaultReader(BMallocIO &buf, const char *uri, bool compressd)
{
	char host[256], subdir[256], name[256];
	uri2host(uri, host, subdir);
	if (*subdir){
		sprintf(name, "%s/bbsmenu.html", subdir);
	}else{
		strcpy(name, "bbsmenu.html");
	}
	
	http_info hinfo;
	hinfo.init(compressd);

	BMallocIO sjisBuf;
	//TODO get()の戻り型を考える。
	status_t sts = get(host, name, sjisBuf, hinfo);

	if(sts == B_ERROR || sts != HTTP_OK) return B_ERROR; 

	//utf8に変換。
	sjisBuf.Seek(0, SEEK_SET);
	cutf8(&sjisBuf, &buf);
		
	return B_OK;
}
////////////////////////////////////////////////////////////////////////////////
// defaultLocalReader
//
// 2008/4/17: 成功時にはB_OKを返すようにする。
////////////////////////////////////////////////////////////////////////////////
status_t
BBSHandler::defaultLocalReader()
{
	BFile loadfile;
	BDirectory dir;
	findcachedir(&dir, kBBS_DIR_NAME);
	status_t status = loadfile.SetTo(&dir, this->GetName(), B_READ_ONLY);
	if(status != B_OK) return B_ERROR;

	char readbuf[256];
	fBuf.Seek(0, SEEK_SET);
	for(;;){
		ssize_t readsize = loadfile.Read(readbuf, sizeof(readbuf));
		if (readsize <= 0) break;
		fBuf.Write(readbuf, readsize);
	}
	
	return B_OK;
}
////////////////////////////////////////////////////////////////////////////////
// Expanded
// cacheファイルにあるBMessageを取り出す
//
////////////////////////////////////////////////////////////////////////////////
status_t
BBSHandler::Expanded(BMessage *archive)
{
	BDirectory dir;
	findcachedir(&dir, kBBS_DIR_NAME);
	BFile file(&dir, this->GetName(), B_READ_ONLY);
	if (file.InitCheck() != B_OK) return B_ERROR;
	
	attr_info info;
	/* nameが重複していた場合はどうなるんだぁ */
	if (file.GetAttrInfo(kBBS_ARCHIVE, &info) == B_OK) {
		char data[info.size]; //gcc only ?
		if (file.ReadAttr(kBBS_ARCHIVE, info.type, 0, data, info.size) == info.size) {
			archive->Unflatten(data);
		}
	}
	
	return B_OK;
}
////////////////////////////////////////////////////////////////////////////////
// RemakeIndex
// BoardItemの内容をBMessageにしてファイルへ格納
//
////////////////////////////////////////////////////////////////////////////////
void
BBSHandler::WriteExpanded(BoardOutLineListView *owner)
{
	BDirectory dir;
	findcachedir(&dir, kBBS_DIR_NAME);
	BFile file(&dir, this->GetName(), B_READ_ONLY);
	if (file.InitCheck() != B_OK) return;

	BMessage archive;
	BoardItem *board;
	for (int32 i= 0; (board = (BoardItem *)owner->ItemAt(i)); i++) {
		if (board->OutlineLevel() == 0) {
			archive.AddBool(board->Text(), board->IsExpanded());
		}
	}
	archive.AddRect("bounds", owner->Bounds());
	
	ssize_t size = archive.FlattenedSize();
	char data[size];
	archive.Flatten(data, size);
	file.WriteAttr(kBBS_ARCHIVE, B_MESSAGE_TYPE, 0, data, size);
}
////////////////////////////////////////////////////////////////////////////////
// getGroupName
// bbsmenuよりグループ名を取得
//
////////////////////////////////////////////////////////////////////////////////
inline int
getGroupName(const char * in, char * name)
{
	const char *start = in;
	
	while(*in && (*in != '<' && (*(in + 1) != '/'))){
		*name++ = *in++;
	}
	*name = '\0';

	return in - start;
}
////////////////////////////////////////////////////////////////////////////////
// getBoardInfo
// bbsmenuより板情報（名称およびuri）を取得。
//
////////////////////////////////////////////////////////////////////////////////
inline int
getBoardInfo(const char *in, char *name, char *url)
{
	const char *start = in;
	
	//url取得
	//20050926 " _TARGET" 対策。
	while(*in != '>' && *in != ' ') {*url++ = *in++;}
	*url = '\0';
	
	while(*in != '>' ) {in++;}
	in++; //skip '>'

	//名称取得
	while(*in && (*in != '<' && *(in + 1) != '/')){
		*name++ = *in++;
	}
	*name = '\0';
	return in - start;
}
////////////////////////////////////////////////////////////////////////////////
// 
//
//
////////////////////////////////////////////////////////////////////////////////
void
BBSHandler::AddBBSItem(BoardOutLineListView *owner, const char *boardname, const char *uri)
{
	if (!fSuperItem) return;
	owner->AddUnder(new BoardItem(boardname, uri, BOARD_NORMAL),  fSuperItem);
}
////////////////////////////////////////////////////////////////////////////////
// 
//
//
////////////////////////////////////////////////////////////////////////////////
void
BBSHandler::AddBBSSuperItem(BoardOutLineListView *owner, const char *catname, bool expanded)
{
	fSuperItem = new BoardItem(catname, NULL, BOARD_CATEGORY, 0, expanded);
	owner->AddItem(fSuperItem);
}
////////////////////////////////////////////////////////////////////////////////
// 
//
//
////////////////////////////////////////////////////////////////////////////////
// 2005.10.22:BOutlineView::AddUnder()の逆順表示対策のため、スタックに入れるようにした。
//
status_t
BBSHandler::defaultDrawer(BoardListView *owner, const char *buf, size_t size, const char *startcat, const char *endcat)
{
	Stack <BoardItem *> itemstack;
	const char *s = buf;
	const char *se = s + size;
	BoardOutLineListView *outlineView = owner->OutLineView();
	bool boardchecked = false;
	bool boardstarted = false;
	if(!startcat) boardstarted = true;

	BMessage archive;
	if (this->Expanded(&archive) != B_OK) return B_ERROR;
	
	while(s <= se) { 

		if (*s++ != '<') {
			if(owner->fTreadRunner->StopLoading()) goto end;
			continue;
		}
	
		if(*s =='B' && strncmp(s, "BR><BR><B>", 10) == 0){
			char groupName[kNAMEBUFSIZ];
			if(owner->fTreadRunner->StopLoading()) goto end;
			s += getGroupName(s + 10, groupName);
			s += 10;

			if(!boardstarted){
				if(strcmp(groupName, startcat)) continue;
			}
			boardstarted = true;

			//一つ前のsuperitemに対するitemを表示(FILO)。
			BoardItem *item;
			while(itemstack.Pop(&item)){
				if (owner->fTreadRunner->StopLoading() || !owner->Window()->Lock()) goto end;
				outlineView->AddUnder(item,  fSuperItem);
				owner->Window()->Unlock();
			}

			if(strcmp(groupName , endcat)== 0) goto end;
			if (owner->fTreadRunner->StopLoading() || !owner->Window()->Lock()) goto end;
			bool expanded = true;
			if (archive.FindBool(groupName, &expanded) != B_OK)
				expanded = true;
			this->AddBBSSuperItem(outlineView, groupName, expanded);
			owner->Window()->Unlock();
			boardchecked = true;

		}

		if(boardchecked && *s == 'A' && strncmp(s, "A HREF=", 7) == 0 ){
			if(owner->fTreadRunner->StopLoading()) goto end;
			char boardName[kNAMEBUFSIZ], boardUrl[kNAMEBUFSIZ];
			s += getBoardInfo(s + 14, boardName, boardUrl);
			s += 14;
			itemstack.Push(new BoardItem(boardName, boardUrl, BOARD_NORMAL));
		}

	}
	if (owner->Window()->Lock()) {
		//一つ前のsuperitemに対するitemを表示(FILO)。
		BoardItem *item;
		while(itemstack.Pop(&item))
			outlineView->AddUnder(item, fSuperItem);
		owner->Window()->Unlock();
	}

end:
	outlineView->Window()->Lock();
	BRect bounds;
	if (archive.FindRect("bounds", &bounds) == B_OK) {
		outlineView->ScrollTo(0, bounds.top);
	} else archive.AddRect("bounds", outlineView->Bounds());
	outlineView->Window()->Unlock();

	return B_OK;
}
////////////////////////////////////////////////////////////////////////////////
// 
//
//
////////////////////////////////////////////////////////////////////////////////
void
BBSHandler::loadDatfile()
{
}
#if 0
////////////////////////////////////////////////////////////////////////////////
// 
//
//
////////////////////////////////////////////////////////////////////////////////
PrefView *
BBSHandler::makePreference()
{
	return NULL;
}

PrefView *
BBSHandler::PreferenceView()
{
	if (!fPrefView) 	fPrefView = this->makePreference();
	return fPrefView;
} 
#endif
